using System;
using System.Security.Principal;
using AndyPike.Commons;
using AndyPike.Commons.Extensions;
using AndyPike.Commons.Persistence;
using AndyPike.Snowflake.Core.Exceptions;
using AndyPike.Snowflake.Core.Mappers;
using AndyPike.Snowflake.Core.Messages;
using AndyPike.Snowflake.Core.Queries.Accounts;
using AndyPike.Snowflake.Entities;
using Castle.Services.Transaction;

namespace AndyPike.Snowflake.Core.Services
{
    [Transactional]
    public class AccountService : IAccountService
    {
        private readonly IRepository<Account> accountRepository;

        public AccountService(IRepository<Account> accountRepository)
        {
            GuardAgainst.ArgumentNull(accountRepository, "accountRepository");

            this.accountRepository = accountRepository;
            RegistrationToAccountMapper = new RegistrationMessageToAccountMapper();
            AccountToAccountInfoMessageMapper = new AccountToAccountInfoMessageMapper();
        }

        public IMessageToEntityMapper<RegistrationMessage, Account> RegistrationToAccountMapper { get; set; }
        public IEntityToMessageMapper<Account, AccountInfoMessage> AccountToAccountInfoMessageMapper { get; set; }

        [Transaction]
        public Guid RegisterAccount(RegistrationMessage registration)
        {
            GuardAgainst.ArgumentNull(registration, "registration");

            registration.Validate();

            Account accountWithSameUserName = accountRepository.FindOne(new AccountByUserName(registration.UserName));
            if(accountWithSameUserName != null)
                throw new DuplicateUserNameException();

            Account account = RegistrationToAccountMapper.Map(registration);

            accountRepository.Save(account);

            return account.Id;
        }

        public IPrincipal RetrievePrincipleFromAuthenticationToken(Guid authenticationToken)
        {
            GuardAgainst.ArgumentEmpty(authenticationToken, "authenticationToken");

            Account account = accountRepository.FindById(authenticationToken);

            if(account == null) throw new InvalidAuthenticationTokenException(string.Format("Unable to find an account from the authentication token '{0}'", authenticationToken));

            return AccountToAccountInfoMessageMapper.Map(account);
        }

        public Guid AuthenticateCredentials(CredentialsMessage credentials)
        {
            GuardAgainst.ArgumentNull(credentials, "credentials");

            credentials.Validate();

            Account account = accountRepository.FindOne(new AccountByUserNameAndPassword(credentials.UserName, credentials.Password));

            if (account == null) throw new AuthenticationFailedException();

            return account.Id;
        }
    }
}